package com.ljl.module.system.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.ljl.module.system.dao.ResourceDao;
import com.ljl.module.system.domain.*;
import com.ljl.module.system.service.FfmpegService;
import com.ljl.module.system.service.ResourceService;
import com.ljl.module.system.util.Callback;
import com.ljl.module.system.util.ResourceState;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author ljl
 */
@Slf4j
@Service
public class ResourceServiceImpl implements ResourceService {
    private final BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
    private final ExecutorService executor = new ThreadPoolExecutor(
            4, 4,
            0, TimeUnit.MILLISECONDS,
            workQueue,
            new CustomizableThreadFactory("merge-pool"));

    @Autowired
    private ResourceDao resourceDao;

    @Autowired
    private FfmpegService ffmpegService;

    @Override
    public Map<String, Object> getResources(String filePath, Page page) {
        return null;
    }

    @Override
    public Resource save(Resource resource) {
        if (!StringUtils.hasText(resource.getUuid())) {
            return null;
        }
        return resourceDao.save(resource);
    }

    @Override
    public Resource findByUuid(String uuid) {
        return resourceDao.findByUuid(uuid);
    }

    @Override
    public List<Resource> findAll() {
        return resourceDao.findAll();
    }

    @Override
    public Resource remove(String uiid) {
        return resourceDao.remove(uiid);
    }

    @Override
    public ApiResponse<String> merge(String uuid) {
        Resource resource = this.findByUuid(uuid);
        if (resource == null) {
            return new ApiResponse<>(400, "参数错误", "资源不存在：" + uuid);
        }
        if (resource.getState() != ResourceState.NONE) {
            return new ApiResponse<>(400, "任务状态异常", JSONObject.toJSONString(resource));
        }
        resource.setState(ResourceState.ADDED);
        MergeTask task = new MergeTask();
        task.setResource(resource);
        this.doMerge(task);
        return new ApiResponse<>(0, "添加完成", null);
    }

    @Override
    public List<Resource> analyze(String path) throws Exception {
        List<Resource> result = new ArrayList<>();
        // 遍历目录，找到符合的文件夹
        File filePath = new File(path);
        if (!filePath.exists()) {
            throw new IllegalArgumentException(String.format("路径不存在：%s", path));
        }
        if (!filePath.isDirectory()) {
            throw new IllegalArgumentException(String.format("该路径不是一个文件夹：%s", path));
        }
        File[] files = filePath.listFiles();
        if (files == null || files.length == 0) {
            return result;
        }
        List<File> directoryList = new ArrayList<>();
        findAllDirectory(filePath, directoryList);
        for (File directory : directoryList) {
            Resource resource = findResource(directory);
            if (resource != null) {
                result.add(resource);
            }
        }
        return result;
    }

    /**
     * 遍历获取当前文件夹下的全部文件夹
     *
     * @param current  当前文件夹
     * @param fileList 文件夹集合
     */
    private void findAllDirectory(File current, List<File> fileList) {
        if (current.isDirectory()) {
            fileList.add(current);
        }
        File[] files = current.listFiles();
        if (files != null && files.length != 0) {
            for (File file : files) {
                findAllDirectory(file, fileList);
            }
        }
    }

    private Resource findResource(File baseFilePath) {
        if (baseFilePath == null || !baseFilePath.exists() || baseFilePath.isFile()) {
            return null;
        }
        File[] listFiles = baseFilePath.listFiles();
        if (listFiles == null || listFiles.length == 0) {
            return null;
        }
        // 判断是否为电脑缓存的资源
        Resource res = getPcVideo(listFiles);
        // 判断是否为手机版缓存的资源
        if (res == null) {
            res = getMobieVideo(listFiles);
        }
        return res;
    }

    private Resource getPcVideo(File[] files) {
        if (files == null || files.length == 0) {
            return null;
        }
        // 必需的文件
        File videoInfoFile = null;
        File playurlFile = null;
        for (File file : files) {
            String fileName = file.getName();
            if (".videoInfo".equalsIgnoreCase(fileName)) {
                videoInfoFile = file;
            }
            if (".playurl".equalsIgnoreCase(fileName)) {
                playurlFile = file;
            }
        }
        // 这两个文件必须存在，否则判断不了视频的信息
        if (videoInfoFile == null || playurlFile == null) {
            return null;
        }
        String baseFilePath = videoInfoFile.getParent();
        try (FileInputStream videoInfoInput = new FileInputStream(videoInfoFile);
             FileInputStream playurlInput = new FileInputStream(playurlFile);) {
            // 获取文件信
            JSONObject videoJson = JSONObject.parseObject(videoInfoInput, JSONObject.class);
            String groupId = videoJson.getString("groupId");
            String uname = videoJson.getString("uname");
            String groupTitle = videoJson.getString("groupTitle");
            String title = videoJson.getString("title");
            Integer page = videoJson.getInteger("p");
            // 获取音视频信息
            JSONObject playurlJson = JSONObject.parseObject(playurlInput, JSONObject.class);
            JSONObject data = playurlJson.getJSONObject("data");
            if (data == null) {
                data = playurlJson.getJSONObject("result");
            }
            JSONObject dash = data.getJSONObject("dash");
            // 视频
            JSONObject video = dash.getJSONArray("video").getJSONObject(0);
            String videoBaseUrl = video.getString("baseUrl");
            if (videoBaseUrl == null) {
                videoBaseUrl = video.getString("base_url");
            }
            String videoFileName = getFileNameFromUrl(videoBaseUrl);
            String videoFilePath = Paths.get(baseFilePath, videoFileName).toString();

            // 音频
            JSONObject audio = dash.getJSONArray("audio").getJSONObject(0);
            String audioBaseUrl = audio.getString("baseUrl");
            if (audioBaseUrl == null) {
                audioBaseUrl = audio.getString("base_url");
            }
            String audioFileName = getFileNameFromUrl(audioBaseUrl);
            String audioFilePath = Paths.get(baseFilePath, audioFileName).toString();

            Resource resource = new Resource();
            resource.setGroupId(groupId);
            resource.setGroupTitle(groupTitle);
            resource.setOwner(uname);
            resource.setTitle(title);
            resource.setPage(page);
            resource.setBasePath(baseFilePath);
            resource.setVideoPath(videoFilePath);
            resource.setAudioPath(audioFilePath);
            resource.setFromPhone(false);
            return resource;
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return null;
    }

    private Resource getMobieVideo(File[] files) {
        if (files == null || files.length == 0) {
            return null;
        }
        // 必需的文件
        File entryFile = null;
        for (File file : files) {
            String fileName = file.getName();
            if ("entry.json".equalsIgnoreCase(fileName)) {
                entryFile = file;
                break;
            }
        }
        // 这两个文件必须存在，否则判断不了视频的信息
        if (entryFile == null) {
            return null;
        }
        String baseFilePath = entryFile.getParent();
        try (FileInputStream entryInput = new FileInputStream(entryFile)) {
            // 获取文件信
            JSONObject entryJson = JSONObject.parseObject(entryInput, JSONObject.class);
            String groupId = entryJson.getString("bvid");
            String uname = entryJson.getString("owner_name");
            String groupTitle = entryJson.getString("title");
            JSONObject pageData = entryJson.getJSONObject("page_data");
            String title = pageData.getString("part");
            Integer page = pageData.getInteger("page");

            String typeTag = entryJson.getString("type_tag");
            File videoFile = new File(Paths.get(baseFilePath, typeTag, "video.m4s").toString());
            File audioFile = new File(Paths.get(baseFilePath, typeTag, "audio.m4s").toString());
            String videoFilePath = null;
            String audioFilePath = null;
            if (videoFile.exists()) {
                videoFilePath = videoFile.getAbsolutePath();
            }
            if (audioFile.exists()) {
                audioFilePath = audioFile.getAbsolutePath();
            }
            if (videoFilePath == null || audioFilePath == null) {
                File indexFile = new File(Paths.get(baseFilePath, typeTag, "index.json").toString());
                try (FileInputStream indexInput = new FileInputStream(indexFile)) {
                    // 获取文件信
                    JSONObject indexJson = JSONObject.parseObject(indexInput, JSONObject.class);
                    // 视频
                    JSONObject video = indexJson.getJSONArray("video").getJSONObject(0);
                    String videoBaseUrl = video.getString("baseUrl");
                    if (videoBaseUrl == null) {
                        videoBaseUrl = video.getString("base_url");
                    }
                    String videoFileName = getFileNameFromUrl(videoBaseUrl);
                    videoFilePath = Paths.get(baseFilePath, typeTag, videoFileName).toString();
                    // 音频
                    JSONObject audio = indexJson.getJSONArray("audio").getJSONObject(0);
                    String audioBaseUrl = audio.getString("baseUrl");
                    if (audioBaseUrl == null) {
                        audioBaseUrl = audio.getString("base_url");
                    }
                    String audioFileName = getFileNameFromUrl(audioBaseUrl);
                    audioFilePath = Paths.get(baseFilePath, typeTag, audioFileName).toString();
                }
            }
            Resource resource = new Resource();
            resource.setGroupId(groupId);
            resource.setGroupTitle(groupTitle);
            resource.setOwner(uname);
            resource.setTitle(title);
            resource.setPage(page);
            resource.setBasePath(baseFilePath);
            resource.setVideoPath(videoFilePath);
            resource.setAudioPath(audioFilePath);
            resource.setFromPhone(true);
            return resource;
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return null;
    }

    private String getFileNameFromUrl(String url) {
        if (url == null) {
            return null;
        }
        int i1 = url.indexOf(".m4s");
        if (i1 != -1) {
            String s1 = url.substring(0, i1);
            int i2 = s1.lastIndexOf("/");
            if (i2 != -1) {
                return s1.substring(i2 + 1) + ".m4s";
            }
        } else {
            int i3 = url.indexOf("?");
            if (i3 != -1) {
                String s2 = url.substring(0, i3);
                int i4 = s2.lastIndexOf("/");
                if (i4 != -1) {
                    return s2.substring(i4 + 1);
                }
            }
        }
        return null;
    }

    private long decodePcVideo(String input, String output, Callback<Long> process) throws Exception {
        if (!StringUtils.hasText(input) || !StringUtils.hasText(output)) {
            log.error("文件路径为空, input {}, output {}", input, output);
            throw new FileNotFoundException(String.format("文件路径为空, video: %s, audio: %s", input, output));
        }
        File inputFile = new File(input);
        if (!inputFile.exists()) {
            log.error("文件不存在, input {}", input);
            throw new FileNotFoundException(String.format("文件不存在, input %s", input));
        }
        File file = new File(output);
        if (!file.getParentFile().exists()) {
            boolean mkdirs = file.getParentFile().mkdirs();
            if (!mkdirs) {
                throw new IllegalArgumentException(String.format("无法创建文件夹: %s", output));
            }
        }
        long writeLength = 0;
        try (FileInputStream inputStream = new FileInputStream(inputFile);
             FileOutputStream os = new FileOutputStream(output)) {
            int cnt = 0;
            int read;
            boolean flag = true;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) > 0) {
                if (flag) {
                    flag = false;
                    boolean needRemove = true;
                    for (int i = 0; i < 9; i++) {
                        if (bytes[i] != (byte) 0x30) {
                            needRemove = false;
                            break;
                        }
                    }
                    if (needRemove) {
                        bytes = Arrays.copyOfRange(bytes, 9, bytes.length);
                        read -= 9;
                    }
                }
                writeLength += read;
                os.write(bytes, 0, read);
                if (cnt++ > 100) {
                    cnt = 0;
                    process.call(writeLength);
                    os.flush();
                }
            }
            os.flush();
        }
        return writeLength;
    }

    private void doMerge(MergeTask task) {
        Resource resource = task.getResource();
        resource.setState(ResourceState.PENDING);
        executor.submit(() -> {
            // 如果是电脑的资源，则需要解密
            if (!resource.isFromPhone()) {
                resource.setState(ResourceState.DECODING);
                String videoPath = resource.getVideoPath();
                String audioPath = resource.getAudioPath();
                if (!StringUtils.hasText(videoPath) || !StringUtils.hasText(audioPath)) {
                    log.error("解密文件路径缺失, videoPath {}, audioPath {}", videoPath, audioPath);
                    resource.setState(ResourceState.FAILED);
                    resource.setError(String.format("解密文件路径缺失, videoPath: %s, audioPath: %s", videoPath, audioPath));
                    return;
                }
                File videoFile = new File(videoPath);
                File audioFile = new File(audioPath);
                if (!videoFile.exists() || !audioFile.exists()) {
                    log.error("解密视频文件不存在, videoPath {}, audioPath {}", videoPath, audioPath);
                    resource.setState(ResourceState.FAILED);
                    resource.setError(String.format("解密视频文件不存在, videoPath: %s", videoPath));
                    return;
                }
                long videoLength = videoFile.length();
                long audioLength = audioFile.length();
                BigDecimal total = BigDecimal.valueOf(videoLength + audioLength);
                Random random = new Random();
                String decodeVideoPath = Paths.get(".temp", System.currentTimeMillis() + random.nextInt(1000) + ".m4s").toString();
                String decodeAudioPath = Paths.get(".temp", System.currentTimeMillis() + random.nextInt(1000) + ".m4s").toString();
                resource.setDecodeVideoPath(decodeVideoPath);
                resource.setDecodeAudioPath(decodeAudioPath);
                try {
                    long tempLength = decodePcVideo(videoPath, decodeVideoPath, p -> {
                        BigDecimal current = BigDecimal.valueOf(p);
                        BigDecimal progress = current.divide(total, 2, RoundingMode.HALF_UP);
                        if (progress.compareTo(BigDecimal.valueOf(100)) > -1) {
                            resource.setDecodeProcess(100f);
                        } else {
                            resource.setDecodeProcess(progress.floatValue());
                        }
                    });
                    decodePcVideo(audioPath, decodeAudioPath, currentLength -> {
                        BigDecimal current = BigDecimal.valueOf(tempLength + currentLength);
                        BigDecimal p = current.divide(total, 2, RoundingMode.HALF_UP);
                        if (p.compareTo(BigDecimal.valueOf(100)) > -1) {
                            resource.setDecodeProcess(100f);
                        } else {
                            resource.setDecodeProcess(p.floatValue());
                        }
                    });
                    resource.setDecodeProcess(100f);
                } catch (Exception e) {
                    log.error("文件解密时出现异常: {}", e.getMessage());
                    resource.setState(ResourceState.FAILED);
                    resource.setError(String.format("文件解密时出现异常: %s", e.getMessage()));
                    return;
                }
            }
            resource.setState(ResourceState.TRANSFERRING);
            String videoPath = resource.getVideoPath();
            String audioPath = resource.getAudioPath();
            resource.setOutputPath(new File(System.currentTimeMillis() + ".mp4").getAbsolutePath());
            TransferJob job = new TransferJob();
            job.setUuid(resource.getUuid());
            job.setVideoPath(videoPath);
            job.setAudioPath(audioPath);
            job.setOutputPath(resource.getOutputPath());
            if (!resource.isFromPhone()) {
                job.setVideoPath(resource.getDecodeVideoPath());
                job.setAudioPath(resource.getDecodeAudioPath());
            }
            try {
                boolean flag = ffmpegService.mergeAudioAndVideo(job, resource::setMergeProcess);
                if (flag) {
                    resource.setState(ResourceState.SUCCEED);
                } else {
                    resource.setState(ResourceState.FAILED);
                    resource.setError("ffmpeg合并时失败，请检查日志");
                }
            } catch (Exception e) {
                log.error("合并视频时出错：{}", e.getMessage());
                resource.setState(ResourceState.FAILED);
                resource.setError(e.getMessage());
            }
        });
    }

}
